﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Percolation
{
    public static class NetGenerator
    {
        [Serializable]
        private class NetBase : NetInterface
        {
            protected int m_width;
            protected int m_height;
            protected int[,] m_array;

            public const int MinSize = 10;
            public const int MaxSize = 150;

            public NetBase(int width, int height)
            {
                if (width < MinSize || width > MaxSize)
                    throw new ArgumentException("Percolation.NetGenerator.NetBase.NetBase() - error in 'width' parameter");
                if (height < MinSize || height > MaxSize)
                    throw new ArgumentException("Percolation.NetGenerator.NetBase.NetBase() - error in 'height' parameter");
                
                m_width = width;
                m_height = height;
                m_array = new int [m_width, m_height];

                for (int i = 0; i < m_width; ++i)
                {
                    for (int j = 0; j < m_height; ++j)
                        m_array[i, j] = NetCellColor.White;
                }
            }

            public virtual int getWidth()
            {
                return m_width;
            }

            public virtual int getHeight()
            {
                return m_height;
            }

            public virtual int getValue(int x, int y)
            {
                if (x < 0 || x >= m_width)
                {
                    String message = String.Format("Percolation.NetGenerator.NetBase.getValue(int x, int y) - error in 'x' parameter, 'x' = {0}", x);
                    throw new IndexOutOfRangeException(message);
                }
                if (y < 0 || y >= m_height)
                {
                    String message = String.Format("Percolation.NetGenerator.NetBase.getValue(int x, int y) - error in 'y' parameter, 'y' = {0}", y);
                    throw new IndexOutOfRangeException(message);
                }
                return m_array[x, y];
            }
        }

        [Serializable]
        private class RandomNet: NetBase
        {
            public RandomNet(int width, int height, double probability, System.Random generator)
                : base(width, height)
            {
                int x = 0, y = 0;
                if (generator == null)
                    generator = new Random();
                double numBlack = probability*getWidth()*getHeight(); //число черных узлов

                int i = 0;
                while (i < numBlack)
                {
                    x = generator.Next(0, getWidth());
                    y = generator.Next(0, getHeight());

                    if (getValue(x, y) == NetCellColor.White)
                    {
                        m_array[x, y] = NetCellColor.Black;
                        ++i;
                    }
                }
            }
        }

        [Serializable]
        private class ChessNet : NetBase
        {
            public ChessNet(int width, int height)
                : base(width, height)
            {
                for (int i = 0; i < getWidth(); ++i)
                {
                    for (int j = 0; j < getHeight(); ++j)
                    {
                        if (((i + j) % 2) == 1)
                            m_array[i, j] = NetCellColor.Black;
                    }
                }
            }
        }

        [Serializable]
        private class StripRowNet : NetBase
        {
            public StripRowNet(int width, int height)
                : base(width, height)
            {
                for (int i = 0; i < getWidth(); ++i)
                {
                    for (int j = 0; j < getHeight(); ++j)
                    {
                        if ((i % 2) == 1)
                            m_array[i, j] = NetCellColor.Black;
                    }
                }
            }
        }

        [Serializable]
        private class StripColumnNet : NetBase
        {
            public StripColumnNet(int width, int height)
                : base(width, height)
            {
                for (int i = 0; i < getWidth(); ++i)
                {
                    for (int j = 0; j < getHeight(); ++j)
                    {
                        if ((j % 2) == 1)
                            m_array[i, j] = NetCellColor.Black;
                    }
                }
            }
        }

        [Serializable]
        private class RingClosedNet : NetBase
        {
            public RingClosedNet(int width, int height)
                : base(width, height)
            {
                int size = Math.Min(getWidth(), getHeight());
                for (int i = 0; i < getWidth(); ++i)
                    for (int j = 0; j < getHeight(); ++j)
                    {
                        if ((i % 2 == 0) && (i < size / 2) && (j >= i) && (j < size - i)) 
                            m_array[i, j] = NetCellColor.Black;
                        else if ((j % 2 == 0) && (j < size / 2) && (i >= j) && (i < size - j)) 
                            m_array[i, j] = NetCellColor.Black;
                        else if (i + j > size - 1) 
                            m_array[i, j] = m_array[size - j - 1, size - i - 1];
                    }
            }
        }

        [Serializable]
        private class RingHollowNet : NetBase
        {
            public RingHollowNet(int width, int height)
                : base(width, height)
            {
                int size = Math.Min(getWidth(), getHeight());
                for (int i = 0; i < size; ++i)
                    for (int j = 0; j < size; ++j)
                    {
                        if ((i % 2 == 0) && (((i <= size / 2) && (j >= i) && (j <= size - i)) 
                            || (i > size / 2) && (j >= size - i) && (j <= i))) 
                            m_array[i, j] = NetCellColor.Black;
                        else if ((j % 2 == 0) && (((j <= size / 2) && (i >= j) && (i <= size - j)) 
                            || (j > size / 2) && (i >= size - j) && (i <= j))) 
                            m_array[i, j] = NetCellColor.Black;

                    }

            }
        }

        [Serializable]
        private class RainNet : NetBase
        {
            public RainNet(int width, int height)
                : base(width, height)
            {
                for (int i = 0; i < getWidth(); ++i)
                    for (int j = 0; j < getHeight(); ++j)
                        if (((j % 2) == 1) && (((j + i) % 5 % 2 == 0) || ((j + i) % 11 == 0))) 
                            m_array[i, j] = NetCellColor.Black;
            }
        }

        [Serializable]
        private class DottedRowNet : NetBase
        {
            public DottedRowNet(int width, int height)
                : base(width, height)
            {
                for (int i = 0; i < getWidth(); ++i)
                    for (int j = 0; j < getHeight(); ++j)
                        if ((((i % 2) == 0) && ((i / 10) % 2 == 1) && ((j + i / 2) % 2 == 1))
                          || (((i % 2) == 1) && ((i / 10) % 2 == 0) && (i % 10 != 9) && ((j + i / 2) % 2 == 0))) 
                            m_array[i, j] = NetCellColor.Black;
            }
        }

        [Serializable]
        private class DottedColumnNet : NetBase
        {
            public DottedColumnNet(int width, int height)
                : base(width, height)
            {
                for (int i = 0; i < getWidth(); ++i)
                    for (int j = 0; j < getHeight(); ++j)
                        if ((((j % 2) == 0) && ((j / 10) % 2 == 1) && ((i + j / 2) % 2 == 1))
                          || (((j % 2) == 1) && ((j / 10) % 2 == 0) && (j % 10 != 9) && ((i + j / 2) % 2 == 0))) 
                            m_array[i, j] = NetCellColor.Black;
            }
        }

        [Serializable]
        private class HTestNet : NetBase
        {
            public HTestNet(int width, int height)
                : base(width, height)
            {
                for (int i = 0; i < getWidth(); ++i)
                    for (int j = 0; j < getHeight(); ++j)
                        if (((j % 10 < 5) && ((i % 10 == 0) || (i % 10 == 4)))
                        || ((j % 10 == 2) && (i % 10 > 0) && (i % 10 < 4)))
                            m_array[i, j] = NetCellColor.Black;
            }
        }

        [Serializable]
        private class ShiftedHTestNet : NetBase
        {
            public ShiftedHTestNet(int width, int height)
                : base(width, height)
            {
                for (int i = 0; i < getWidth(); ++i)
                    for (int j = 0; j < getHeight(); ++j)
                        if (((j % 10 < 5) && ((i % 10 == 0) || (i % 10 == 4)))
                        || ((j % 10 == 2) && (i % 10 > 0) && (i % 10 < 4))
                        || (((j % 10 > 4) && ((i % 10 == 5) || (i % 10 == 9)))
                        || ((j % 10 == 7) && (i % 10 > 5) && (i % 10 < 9))))
                            m_array[i, j] = NetCellColor.Black;
            }
        }

        [Serializable]
        private class HalfShiftedHTestNet : NetBase
        {
            public HalfShiftedHTestNet(int width, int height)
                : base(width, height)
            {
                for (int i = 0; i < getWidth(); ++i)
                    for (int j = 0; j < getHeight(); ++j)
                    {
                        if (((j % 4 == 0) && (i % 2 == 0))
                            || (((j % 8 == 5) || (j % 8 == 7)) && ((i % 8 == 2) || (i % 8 == 6)))
                            || ((j % 8 == 6) && (i % 8 > 1) && (i % 8 < 7))
                            || ((i % 4 == 0) && (j % 8 < 5))
                            || ((j % 16 == 2) && (i % 8 < 5))
                            || ((j % 16 == 10) && (i % 8 > 4)))
                            m_array[i, j] = NetCellColor.Black;
                    }
            }
        }

        [Serializable]
        private class VerticalGradientNet: NetBase
        {
            public VerticalGradientNet(int width, int height, double probability, double gradProbability, System.Random generator)
                : base(width, height)
            {
                int y = 0;
                if (generator == null)
                    generator = new Random();

                double probability0 = probability - gradProbability * probability;
                double probability1 = probability + gradProbability * probability;

                double h = (probability1 - probability0) / ((getWidth() / 2) - 1); //шаг вероятности м/ду столбцами
                double curP = probability0;
                for (int i = 0; i < getWidth(); ++i) //левая половина таблицы
                {
                    int k = 0;
                    while (k < curP * getHeight())
                    {
                        y = generator.Next(0, getHeight());
                        if (m_array[i, y] == NetCellColor.White)
                        {
                            m_array[i, y] = NetCellColor.Black;
                            ++k;
                        }
                    }

                    if (i < getWidth() / 2)
                        curP = probability0 + i * h;
                    else
                        curP = probability1 - (i - getWidth()/2) * h;
                }
            }
        }

        [Serializable]
        private class HorizontalGradientNet : NetBase
        {
            public HorizontalGradientNet(int width, int height, double probability, double gradProbability, System.Random generator)
                : base(width, height)
            {
                int x = 0;
                if (generator == null)
                    generator = new Random();

                double probability0 = probability - gradProbability * probability;
                double probability1 = probability + gradProbability * probability;

                double h = (probability1 - probability0) / ((getHeight() / 2) - 1); //шаг вероятности м/ду строками
                double curP = probability0;
                for (int j = 0; j < getHeight(); ++j) //верхняя половина таблицы
                {
                    int k = 0;
                    while (k < curP * getWidth())
                    {
                        x = generator.Next(0, getWidth());
                        if (m_array[x, j] == NetCellColor.White)
                        {
                            m_array[x, j] = NetCellColor.Black;
                            ++k;
                        }
                    }

                    if (j < getHeight() / 2)
                        curP = probability0 + j * h;
                    else
                        curP = probability1 - (j - getHeight() / 2) * h;
                }
            }
        }

        public static Random randomGenerator = new Random();

        public static NetInterface generate(NetParams netParams)
        {
            switch (netParams.NetType)
            {
                case NetTypeEnum.VerGradient:
                {
                    GradientNetParams gParams = (GradientNetParams)netParams;
                    return new VerticalGradientNet(gParams.NetWidth, gParams.NetHeight, gParams.Probability0, gParams.Probability1, randomGenerator);
                }

                case NetTypeEnum.HorGradient:
                {
                    GradientNetParams gParams = (GradientNetParams)netParams;
                    return new HorizontalGradientNet(gParams.NetWidth, gParams.NetHeight, gParams.Probability0, gParams.Probability1, randomGenerator);                
                }

                case NetTypeEnum.Random:
                {
                    RandomNetParams rParams = (RandomNetParams) netParams;
                    return new RandomNet(rParams.NetWidth, rParams.NetHeight, rParams.Probability, randomGenerator);
                }

                case NetTypeEnum.Chess:
                {
                    return new ChessNet(netParams.NetWidth, netParams.NetHeight);
                }

                case NetTypeEnum.StripRow:
                {
                    return new StripRowNet(netParams.NetWidth, netParams.NetHeight);
                }

                case NetTypeEnum.StripColumn:
                {
                    return new StripColumnNet(netParams.NetWidth, netParams.NetHeight);
                }

                case NetTypeEnum.RingClosed:
                {
                    return new RingClosedNet(netParams.NetWidth, netParams.NetHeight);
                }

                case NetTypeEnum.RingHollow:
                {
                    return new RingHollowNet(netParams.NetWidth, netParams.NetHeight);
                }

                case NetTypeEnum.Rain:
                {
                    return new RainNet(netParams.NetWidth, netParams.NetHeight);
                }

                case NetTypeEnum.DottedRow:
                {
                    return new DottedRowNet(netParams.NetWidth, netParams.NetHeight);
                }

                case NetTypeEnum.DottedColumn:
                {
                    return new DottedColumnNet(netParams.NetWidth, netParams.NetHeight);
                }

                case NetTypeEnum.HTest:
                {
                    return new HTestNet(netParams.NetWidth, netParams.NetHeight);
                }

                case NetTypeEnum.ShiftedHTest:
                {
                    return new ShiftedHTestNet(netParams.NetWidth, netParams.NetHeight);
                }

                case NetTypeEnum.HalfShiftedHTest:
                {
                    return new HalfShiftedHTestNet(netParams.NetWidth, netParams.NetHeight);
                }
            }

            return null;
        }
    }
}
